﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Graphics;

namespace Ratatoskr
{
    public class Entity : ParentEntity, IDisposable
    {

        #region Fields & Properties

        public override GraphicsDevice GraphicsDevice
        {
            get { return _parent == null ? null : _parent.GraphicsDevice; }
        }

        protected readonly string _name;
        /// <summary>
        /// Unique identifier for this Entity. Optional.
        /// </summary>
        public string Name
        {
            get { return _name; }
        }

        protected ParentEntity _parent;
        public ParentEntity Parent
        {
            get { return _parent; }
        }

        #endregion

        #region Constructor, Parent Management & Initialization

        /// <summary>
        /// Create a new Entity with the specified parent and submembers. No initialization is performed.
        /// </summary>
        /// <param name="parent">ParentEntity to attach, or null.</param>
        /// <param name="name">Unique string identifier for this Entity, or null.</param>
        /// <param name="components">Components to attach, or null.</param>
        /// <param name="children">Named entities to attach, or null.</param>
        /// <param name="nodes">Anonymous entities to attach, or null.</param>
        public Entity(string name = null)
        {
            _name = name;
        }
        
        /// <summary>
        /// Attach a ParentEntity to this entity, registering in the parent's Nodes list and subscribing to relevant events. Sends a ParentChanged event to submemebers.
        /// </summary>
        /// <param name="parent">ParentEntity to attach</param>
        /// <param name="child">Set false to force a named Entity to be registered anonymously.</param>
        /// <returns>True if successfully attached, false otherwise.</returns>
        public bool Attach(ParentEntity parent, bool child = true)
        {
            if (parent == null)
                throw new ArgumentNullException("Tried to attach a null parent.");
            if (_parent != null && CannotDetach())
                return false;
            if (_name == null)
                child = false;
            if (parent.Register(this, child))
            {
                _Detach();
                _parent = parent;
                _parent.ParentChanged += HandleParentChanged;
                ExtendAttach();
                OnParentChanged(this, new EventArgs());
                Initialize();
                return true;
            }
            return false;
        }

        /// <summary>
        /// Detach the current ParentEntity (if any), clearing registration and subscriptions. Sends a ParentChanged event to submemebers.
        /// <returns>True if parent is null after method completes, false otherwise.</returns>
        /// </summary>
        public bool Detach()
        {
            if (_Detach())
                OnParentChanged(this, new EventArgs());
            if (Parent == null)
                return true;
            return false;
        }

        protected bool _Detach(bool force = false)
        {
            if (_parent == null)
                return false;
            if (CannotDetach() && !force)
                return false;
            ExtendDetach();
            _parent.ParentChanged -= HandleParentChanged;
            _parent.Sever(this);
            _parent = null;
            _initialized = false;
            return true;
        }

        /// <summary>
        /// If not currently initialized but parent is initialized, initialize all submembers then initialize self.
        /// </summary>
        /// <returns>True if all members initialized successfully, false otherwise.</returns>
        public new bool Initialize()
        {
            if (_initialized == false)
            {
                if (_parent != null)
                    if (_parent.Initialized)
                        _Initialize();
                ExtendInitializeWithoutParent();
            }
            return _initialized;
        }

        protected void HandleParentChanged(object sender, EventArgs e)
        {
            if (sender == this)
                throw new CyclicalParentChainException("Entity's parent chain includes itself.");
            ExtendHandleParentChanged(sender, e);
            OnParentChanged(sender, e);
        }

        #endregion
        
        #region IDisposable

        public virtual void Dispose()
        {
            foreach (Component c in _components.Values)
                c.Dispose();
            foreach (Entity e in _nodes)
                if (!e.Detach())
                    e.Dispose();
            _Detach(true);
            ExtendDispose();
        }

        #endregion

        #region Virtual Methods & Implementation Notes

        /// <summary>
        /// Called by Attach() after verifying registration with a new parent and subscribing to its ParentChanged event, but before sending a local ParentChanged event or attempting initialization.
        /// </summary>
        protected virtual void ExtendAttach() { }

        /// <summary>
        /// Checked before detaching, and before attaching if a parent is already present. Aborts if true.
        /// </summary>
        /// <returns>False if detaching from a parent is allowed, true otherwise.</returns>
        protected virtual bool CannotDetach() { return false; }

        /// <summary>
        /// Called by Detach() after verifying current parent is not null, but before any other operations.
        /// </summary>
        protected virtual void ExtendDetach() { }

        /// <summary>
        /// Called when there are changes to the Entity hierarchy above this component's immediate parent, before propegating the event to submembers.
        /// </summary>
        protected virtual void ExtendHandleParentChanged(object sender, EventArgs e) { }

        /// <summary>
        /// Called by Initialize() if this entity's state allows initialization, but its parent is null.
        /// Normal initialization, including ExtendInitializePre() and ExtendInitializePost(), can be performed by calling _Initialize().
        /// </summary>
        protected virtual void ExtendInitializeWithoutParent() { }

        /// <summary>
        /// Called by Dispose() after recursively disposing submembers and detaching from parent. Implement any additional behavior required for clean disposal.
        /// </summary>
        protected virtual void ExtendDispose() { }

        #endregion

    }

    public class CyclicalParentChainException : System.ApplicationException
    {
        public CyclicalParentChainException() { }
        public CyclicalParentChainException(string message) { }
        public CyclicalParentChainException(string message, System.Exception inner) { }
        protected CyclicalParentChainException(System.Runtime.Serialization.SerializationInfo info,
            System.Runtime.Serialization.StreamingContext context) { }
    }
}